package com.moka.db.dao;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

import com.moka.db.Column;
import com.moka.db.Table;
import com.moka.db.dao.BaseDao.DatabaseAccessCallback;

public class Query<T> implements IQuery<T>{

	StringBuffer sql = new StringBuffer(" select * from ");
	
	StringBuffer where = new StringBuffer();
	
	StringBuffer order = new StringBuffer();
	
	StringBuffer limit = new StringBuffer();
	
	List<String> args = new ArrayList<String>(); 
	
	int maxResults = 20;
	
	int firstResult = -1;
	
	SQLiteDatabase db = null;
	
	Class<T> clazz = null;
	
	DatabaseAccessCallback<T> callback;
	
	private static ExecutorService pool = Executors.newFixedThreadPool(5);
	
	public Query(SQLiteOpenHelper helper,Class<T> clazz) {
		
		this.clazz = clazz;
		
		Table table = clazz.getAnnotation(Table.class);
		
		sql.append(table.name());
		
		db = helper.getWritableDatabase();
		
	}

	public Query<T> setCallback(DatabaseAccessCallback<T> callback) {
		
		this.callback = callback;
		
		return this;
	}

	public List<T> xList(){
		
		FutureTask<List<T>> ft = new FutureTask<List<T>>(new Callable<List<T>>() {
			
			public List<T> call() throws Exception {
					
				return getList();
			
			}
			
		});
		
		List<T> list = null;
		
		new Thread(ft).start();
		
		try {
			
			list = ft.get(5l, TimeUnit.SECONDS);
			
		} catch (Exception e) {
			
			e.printStackTrace();
			
		}
		
		return list;
		
	}
	
	private List<T> getList(){
		
		List<T> list = new ArrayList<T>();
		
		if(firstResult > -1){
			
			limit.append(" limit ").append(firstResult).append(",").append(maxResults);
			
		}
		
		sql.append(where).append(order).append(limit);
		
		Object[] objs = args.toArray();
		
		String[] strs = new String[objs.length];
		
		for(int i = 0 ;i<objs.length; i++){
			
			strs[i] = (String)objs[i];
			
		}
		
		Cursor cursor = null;
		
		try{
		
			cursor = db.rawQuery(sql.toString(), strs);
			
			int cursorLen = cursor.getColumnCount();
			
			while(cursor.moveToNext()){
				
				T t = null;
				
				try {
					
					t = clazz.newInstance();
					
				} catch (InstantiationException e) {
					
					e.printStackTrace();
					
				} catch (IllegalAccessException e) {
					
					e.printStackTrace();
					
				}
				
				for(int i = 0;i<cursorLen;i++){
				
					String colsName = cursor.getColumnName(i);
					
					Field field = getFieldByColumnName(t,colsName);
					
					if(field == null){
						
						continue;
						
					}
					
					Column column = field.getAnnotation(Column.class);
					
					Object colsValue = getClosValue(cursor, colsName, column);
					
					if(colsValue!=null){
					
						try {
							
							field.setAccessible(true);
							
							field.set(t, colsValue);
								
						} catch (Exception e) {
							
							e.printStackTrace();
							
						} 
					
					}
				}
				
				list.add(t);
				
			}
			
		}finally{
			
			if(cursor!=null){
			
				cursor.close();
				
			}
			
		}
		
		return list;
	}
	
	public void list() {
		
		Thread thread = new Thread(){
			
			@Override
			public void run() {

				List<T> list = null;
				
				try{
				
					list = getList();
					
				}catch(Exception e){
				
					e.printStackTrace();
					
				}finally{
					
					if(callback!=null){
					
						callback.execute(list);
					
					}
					
				}
				
				
			}
			
		};
		
		pool.execute(thread);
	}
	
	private Object getClosValue(Cursor cursor, String colsName,	Column column) {
		
		 Object colsValue = null;
		
		if(column.type().indexOf("var") > -1){
			
			colsValue = cursor.getString(cursor.getColumnIndex(colsName));
			
		}
		
		if(column.type().indexOf("int") > -1){
		
			colsValue = cursor.getInt(cursor.getColumnIndex(colsName));
		
		}
		
		if(column.type().indexOf("long") > -1){
			
			colsValue = cursor.getLong(cursor.getColumnIndex(colsName));
		
		}
		
		return colsValue;
	}

	private Field getFieldByColumnName(T t,String columnName){
		
		Field[] fields = t.getClass().getDeclaredFields();
		
		for(Field field : fields){
			
			Column column = field.getAnnotation(Column.class);
			
			if(column!=null&&columnName!=null && columnName.equals(column.name())){
				
				return field;
				
			}
			
		}
		
		return null;
		
	}

	public T xUniqueResult(){
		
		FutureTask<T> ft = new FutureTask<T>(new Callable<T>() {
			
			@Override
			public T call() throws Exception {

				T t = null;
				
				Cursor cursor = null;
				
				try{
					
					sql.append(where).append(order);
					
					Object[] objs = args.toArray();
					
					String[] strs = new String[objs.length];
					
					for(int i = 0 ;i<objs.length; i++){
						
						strs[i] = (String)objs[i];
						
					}
					
					cursor = db.rawQuery(sql.toString(), strs);
					
					int cursorLen = cursor.getColumnCount();
					
					if(cursor.moveToFirst()){
						
						try {
							
							t = clazz.newInstance();
							
						} catch (InstantiationException e) {
							
							e.printStackTrace();
							
						} catch (IllegalAccessException e) {
							
							e.printStackTrace();
							
						}
						
						for(int i = 0;i<cursorLen;i++){
						
							String colsName = cursor.getColumnName(i);
							
							Field field = getFieldByColumnName(t,colsName);
							
							if(field == null){
								
								continue;
								
							}
							
							Column column = field.getAnnotation(Column.class);
							
							Object colsValue = getClosValue(cursor, colsName, column);
							
							try {
								
								field.setAccessible(true);
								
								field.set(t, colsValue);
								
							} catch (IllegalArgumentException e) {
								
								e.printStackTrace();
								
							} catch (IllegalAccessException e) {
								
								e.printStackTrace();
								
							}
							
						}
						
					}
					
				}finally{
					
					if(cursor !=null){
						
						cursor.close();
						
					}

				}
				
				return t;
				
			}
			
		});
		
		T t = null;
		
		new Thread(ft).start();
		
		try {
			
			t = ft.get(5l, TimeUnit.SECONDS);
			
		} catch (Exception e) {
			
			e.printStackTrace();
			
		}
		
		return t;
		
	}

	public void uniqueResult() {

		Thread thread = new Thread(){
			
			@Override
			public void run() {

				T t = null;
				
				Cursor cursor = null;
				
				try{
					
					sql.append(where).append(order);
					
					Object[] objs = args.toArray();
					
					String[] strs = new String[objs.length];
					
					for(int i = 0 ;i<objs.length; i++){
						
						strs[i] = (String)objs[i];
						
					}
					
					cursor = db.rawQuery(sql.toString(), strs);
					
					int cursorLen = cursor.getColumnCount();
					
					if(cursor.moveToFirst()){
						
						try {
							
							t = clazz.newInstance();
							
						} catch (InstantiationException e) {
							
							e.printStackTrace();
							
						} catch (IllegalAccessException e) {
							
							e.printStackTrace();
							
						}
						
						for(int i = 0;i<cursorLen;i++){
						
							String colsName = cursor.getColumnName(i);
							
							Field field = getFieldByColumnName(t,colsName);
							
							if(field == null){
								
								continue;
								
							}
							
							Column column = field.getAnnotation(Column.class);
							
							Object colsValue = getClosValue(cursor, colsName, column);
							
							try {
								
								field.setAccessible(true);
								
								field.set(t, colsValue);
								
							} catch (IllegalArgumentException e) {
								
								e.printStackTrace();
								
							} catch (IllegalAccessException e) {
								
								e.printStackTrace();
								
							}
							
						}
						
					}
					
				}finally{
					
					if(cursor !=null){
						
						cursor.close();
						
					}
					
					if(callback!=null){
					
						callback.execute(t);
					
					}

				}
				
			}
			
		};
		
		pool.execute(thread);
	}

	public List<T> xPage(int pageNumber){
		
		firstResult = (pageNumber -1) * maxResults;
		
		return xList();
		
	}
	
	public void page(int pageNumber) {

		firstResult = (pageNumber -1) * maxResults;
		
		list();
	}

	public Query<T> like(String left, String right) {
		
		if(where.length() == 0){
			
			where.append(" where ");
			
		}else{
			
			where.append(" and ");
			
		}
		
		where.append(left).append(" like ? ");
		
		args.add(right);
		
		return this;
	}

	public Query<T> orlike(String left, String right) {
		
		if(where.length() == 0){
			
			where.append(" where ");
		
		}else{
			
			where.append(" or ");
			
		}
		
		where.append(left).append(" like ? ");
		
		args.add(right);
		
		return this;
		
	}

	public Query<T> lt(String left, String right) {

		if(where.length() == 0){
			
			where.append(" where ");
			
		}else{
			
			where.append(" and ");
			
		}
		
		where.append("cast(").append(left).append(" as int)").append("< ? ");

		args.add(right);
		
		return this;
	}

	public Query<T> gt(String left, String right) {

		if(where.length() == 0){
			
			where.append(" where ");
			
		}else{
			
			where.append(" and ");
			
		}

		where.append("cast(").append(left).append(" as int)").append("> ? ");
		
		args.add(right);
		
		return this;
	}

	public Query<T> lte(String left, String right) {

		if(where.length() == 0){
			
			where.append(" where ");
			
		}else{
			
			where.append(" and ");
			
		}
		
		where.append("cast(").append(left).append(" as int)").append("<= ? ");

		args.add(right);
		
		return this;
	}

	public Query<T> gte(String left, String right) {

		if(where.length() == 0){
			
			where.append(" where ");
			
		}else{
			
			where.append(" and ");
			
		}
		
		where.append("cast(").append(left).append(" as int)").append(">= ? ");

		args.add(right);
		
		return this;
	}

	public Query<T> equals(String left, String right) {

		if(where.length() == 0){
			
			where.append(" where ");
			
		}else{
			
			where.append(" and ");
			
		}
		
		where.append(left).append("= ? ");

		args.add(right);
		
		return this;
	}

	public Query<T> notEquals(String left, String right) {

		if(where.length() == 0){
			
			where.append(" where ");
			
		}else{
			
			where.append(" and ");
			
		}
		
		where.append(left).append("<> ? ");

		args.add(right);
		
		return this;
	}

	public Query<T> desc(String field) {
		
		if(order.length() == 0){
			
			order.append(" order by ").append(field).append(" desc ");
			
		}else{
			
			order.append(" , ").append(field).append(" desc ");
			
		}
		
		return this;
	}

	public Query<T> asc(String field) {

		if(order.length() == 0){
			
			order.append(" order by ").append(field).append(" ");
			
		}else{
			
			order.append(" , ").append(field).append(" ");
			
		}
		
		return this;

	}

}
